﻿using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;

namespace EWSEditor.CRM
{
    public class CrmSynchronizationContext
    {
        private string _entityName;
        private Entity _filter;
        private string _fetchXml;
        private bool? _allFilters;
        private EntityCollection _filterCollection;
        private Guid _subscriptionId;

        public int? ObjectTypeCode
        {
            get
            {
                if (_objectTypeCode == null)
                {
                    _objectTypeCode = SharedUtilities.GetTypeCodeFromActType(_entityName);
                    return _objectTypeCode;
                }
                return _objectTypeCode;
            }
            set
            {
                _objectTypeCode = value;
            }
        }

        private int? _objectTypeCode;

        public DateTime LastSyncStartedOn;
        public List<SyncEntity> OmittedResults; // Ommitted Items from ExchangeSyncIdMapping
        public List<SyncEntity> ActualResults; // Removes items that have not changed since the last sync cycle and items that were found in the Omitted Items.
        public List<SyncEntity> Results; //Raw Results from Filters
        public List<SyncEntity> UnchangedItems;

        public CrmSynchronizationContext(string Entityname, Entity Filter, Guid SubscriptionId)
        {
            _entityName = Entityname;
            _filter = Filter;
            _subscriptionId = SubscriptionId;
        
        }

        public void Initialize(bool InitializeAll, EntityCollection FilterCollection = null)
        {
            OmittedResults = GetOmittedResults();

            if (InitializeAll)
            {
                Results = RunMultipleFilters(FilterCollection);
            }
            else
            {
                Results = RunSingleFilter();
            }

            ActualResults = GetFilteredResults();
        }

        private List<SyncEntity> GetOmittedResults()
        {
            QueryByAttribute query = new QueryByAttribute("exchangesyncidmapping");
            query.ColumnSet = new ColumnSet("versionnumber");
            query.Attributes.AddRange("ownerid", "isdeletedinexchange", "isunlinkedincrm", "objecttypecode");
            query.Values.AddRange(((EntityReference)CrmOrgMailboxCache.TargetMailbox["regardingobjectid"]).Id, true, true, ObjectTypeCode);
            EntityCollection results = CrmAuthInfoFactory.GetOrgProxy().RetrieveMultiple(query);

            List<SyncEntity> syncEntities = new List<SyncEntity>();

            foreach (Entity result in results.Entities)
            {
                syncEntities.Add(new SyncEntity(result));
            }

            return syncEntities;
        }

        private List<SyncEntity> GetFilteredResults()
        {
            UnchangedItems = Results.ToArray().ToList<SyncEntity>();

            ////1st Pass: Filter out the manuallyuntrackedobjects and deleted items.
            foreach (SyncEntity deletedObject in OmittedResults)
            {
                if (UnchangedItems.Contains(deletedObject))
                {
                    UnchangedItems.Remove(deletedObject);
                }
            }

            ////2nd Pass: Go through each pruned result and evaluate the modifiedon time of the object. 
            ////If it has been modified, add it to the filteredResults collection.
            LastSyncStartedOn = GetLastSyncStartTime();
            List<SyncEntity> filteredResults = new List<SyncEntity>();

            foreach(SyncEntity unchangedItem in UnchangedItems)
            {
                if ((DateTime)unchangedItem["modifiedon"] > LastSyncStartedOn)
                {
                    filteredResults.Add(unchangedItem);
                }
            }

            ////3rd Pass: Remove items that were added to the filteredResults from the UnchangedItems.
            foreach(SyncEntity filteredResult in filteredResults)
            {
                if (UnchangedItems.Contains(filteredResult))
                {
                    UnchangedItems.Remove(filteredResult);
                }
            }

            return filteredResults;
        }


        public static DateTime GetLastSyncStartTime()
        {
            string xml = ExchangeSyncStateXml.GetCurrentExchangeSyncStateXml();
            ExchangeSyncStateXml syncState = new ExchangeSyncStateXml(xml);
            return syncState.LastSyncStartTime;
        }

        private List<SyncEntity> RunMultipleFilters(EntityCollection Filters)
        {
            List<SyncEntity> results = new List<SyncEntity>();

            foreach (Entity filter in Filters.Entities)
            {
                string modifiedFetch = AddColumnsToFetchXml(filter);
                OrganizationServiceProxy orgProxy = CrmAuthInfoFactory.GetOrgProxy();
                FetchXmlToQueryExpressionResponse queryExpression = SharedUtilities.ConvertFetchToQuery(modifiedFetch, orgProxy);
                EntityCollection filterresults = SharedUtilities.ExecuteQueryWithPaging(queryExpression.Query, orgProxy);

                List<SyncEntity> syncEntities = new List<SyncEntity>();
                foreach (Entity result in filterresults.Entities)
                {
                    SyncEntity syncEntity = new SyncEntity(result);
                    syncEntities.Add(syncEntity);
                }

                IEnumerable<SyncEntity> diffList = syncEntities.Where(x => !results.Any(x1 => x1.Id == x.Id));

                //IEnumerable<SyncEntity> diffList = syncEntities.Except(results);

                foreach (SyncEntity entity in diffList)
                {
                    results.Add(entity);
                }




            }

            //results = null;
            return results;
        }

        private string AddColumnsToFetchXml(Entity Filter)
        {
            string originalFetchXml = (string)Filter["fetchxml"];
            int otc = SharedUtilities.GetTypeCodeFromActType((string)Filter["returnedtypecode"]);

            XmlDocument XmlDoc = new XmlDocument();
            XmlDoc.LoadXml(originalFetchXml);

            if ((otc == 4201) || (otc == 4212) || (otc == 4204) || (otc == 4207) || (otc == 4210) || (otc == 4214) || (otc == 4251))
            {
                XmlNode xNode = XmlDoc.CreateNode(XmlNodeType.Element, "attribute", "");
                XmlAttribute xName = XmlDoc.CreateAttribute("name");
                xName.Value = "subject";
                xNode.Attributes.Append(xName);
                XmlDoc.GetElementsByTagName("entity")[0].InsertAfter(xNode, XmlDoc.GetElementsByTagName("entity")[0].LastChild);
            }
            else
            {
                XmlNode xNode = XmlDoc.CreateNode(XmlNodeType.Element, "attribute", "");
                XmlAttribute xName = XmlDoc.CreateAttribute("name");
                xName.Value = "fullname";
                xNode.Attributes.Append(xName);
                XmlDoc.GetElementsByTagName("entity")[0].InsertAfter(xNode, XmlDoc.GetElementsByTagName("entity")[0].LastChild);
            }

            XmlNode xCreatedOnNode = XmlDoc.CreateNode(XmlNodeType.Element, "attribute", "");
            XmlAttribute xCreatedOnName = XmlDoc.CreateAttribute("name");
            xCreatedOnName.Value = "createdon";
            xCreatedOnNode.Attributes.Append(xCreatedOnName);
            XmlDoc.GetElementsByTagName("entity")[0].InsertAfter(xCreatedOnNode, XmlDoc.GetElementsByTagName("entity")[0].LastChild);

            XmlNode xModifiedOnNode = XmlDoc.CreateNode(XmlNodeType.Element, "attribute", "");
            XmlAttribute xModifiedOnName = XmlDoc.CreateAttribute("name");
            xModifiedOnName.Value = "modifiedon";
            xModifiedOnNode.Attributes.Append(xModifiedOnName);
            XmlDoc.GetElementsByTagName("entity")[0].InsertAfter(xModifiedOnNode, XmlDoc.GetElementsByTagName("entity")[0].LastChild);

            return XmlDoc.InnerXml;
        }

        private List<SyncEntity> RunSingleFilter()
        {
            string fetchXml = AddColumnsToFetchXml(_filter);
            OrganizationServiceProxy orgProxy = CrmAuthInfoFactory.GetOrgProxy();
            FetchXmlToQueryExpressionResponse queryExpression = SharedUtilities.ConvertFetchToQuery(fetchXml, orgProxy);
            EntityCollection filterresults = SharedUtilities.ExecuteQueryWithPaging(queryExpression.Query, orgProxy);
            List<SyncEntity> syncEntities = new List<SyncEntity>();

            foreach (Entity filterresult in filterresults.Entities)
            {
                SyncEntity syncEntity = new SyncEntity(filterresult);
                syncEntities.Add(syncEntity);
            }

            filterresults = null;
            return syncEntities;
        }

        private EntityCollection GetSubscriptionManuallyTrackedObjectTable()
        {
            QueryByAttribute query = new QueryByAttribute("subscriptionmanuallytrackedobject");
            query.ColumnSet = new ColumnSet() { AllColumns = true };
            query.Attributes.AddRange("subscriptionid", "objecttypecode", "track");
            query.Values.AddRange(CrmOrgMailboxCache.SubscriptionId, ObjectTypeCode, true); //False?
            EntityCollection manuallyTrackedObjects = CrmAuthInfoFactory.GetOrgProxy().RetrieveMultiple(query);
            return manuallyTrackedObjects;
        }

        private EntityCollection GetSyncEntryTable()
        {
            QueryByAttribute query = new QueryByAttribute("subscriptionsyncentryoutlook");
            query.ColumnSet = new ColumnSet() { AllColumns = true };
            query.Attributes.AddRange("subscriptionid", "objecttypecode");
            query.Values.AddRange(CrmOrgMailboxCache.SubscriptionId, ObjectTypeCode);
            EntityCollection syncEntryTableRows = CrmAuthInfoFactory.GetOrgProxy().RetrieveMultiple(query);
            return syncEntryTableRows;
        }
    }
}
